package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 聊天室服务端
 */
public class Server {
    /**
     * java.net.ServerSocket
     * 运行在服务端的，主要作用
     * 1:用于向系统申请服务端口，以便客户端可以通过该端口与服务端建立连接
     * 2:监听服务端口，一旦一个客户端连接，则会创建一个Socket，使用该Socket就可以
     * 和建立连接的客户端交互了
     * <p>
     * 如果Socket比喻为一个插座，那么ServerSocket相当于“客服”这边的总机，上面
     * 有一堆插座。
     */
    //private ServerSocket serverSocket;
    //该集合存放所有客户的
   // private List<PrintWriter> allOut = new ArrayList<>();
    private ServerSocket serverSocket;
    private Map<String, PrintWriter> allOut = new HashMap<>();
    public ExecutorService threadPool;
    public Server() {

        try {
            System.out.println("正在启动服务端...");
              /*
                实例化ServerSocket时要指定服务端口，客户端就是通过这个端口
                与服务端建立连接的。
                注意:该端口不能和当前操作系统下的其他应用程序冲突，否则会报错:
                java.net.BindException: Address already in use: JVM_Bind
                如果出现上述异常，解决办法:更换端口号
             */
            serverSocket = new ServerSocket(8088);
            threadPool= Executors.newFixedThreadPool(40);
            System.out.println("服务端启动完毕");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //服务端代码
    public void start() {
        try {
            while (true) {
             /*
                ServerSocket提供的重要方法:
                Socket accept()
                该方法是一个阻塞方法，调用会"卡住"并开始等待客户端的连接，一旦
                一个客户端进行连接，此时accept方法会立即返回一个Socket实例，
                通过这个Socket就可以和连接的客户端交互了。相当于"接电话"操作
             */
                System.out.println("等待客户端连接");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了...");
                //启动一个线程来处理与该客户端的交互
                ClientHandler handler = new ClientHandler(socket);
//                Thread t = new Thread(handler);
//                t.start();
                threadPool.execute(handler);

            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    //该线程任务负责与指定客户交互
    private class ClientHandler implements Runnable {
        private Socket socket;
        private String host;//记录该客户的ip地址
        private String nickname;//记录该客户的昵称

        public ClientHandler(Socket socket) {
            this.socket = socket;
            //通过socket获取客户端的ip地址
            host = socket.getInetAddress().getHostAddress();
        }

        public void run() {
            PrintWriter pw=null;
            try {
             /*
                Socket提供的方法:
                InputStream getInputStream()
                通过该输入流可以读取与当前Socket建立连接的对方计算机发送过来的字节
             */
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);
                //首先单独读取客户端发送过来的第一行字符串,该字符串格式为:昵称
                nickname = br.readLine();

                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
                BufferedWriter bw = new BufferedWriter(osw);
                 pw = new PrintWriter(bw, true);

                //将该输入流存入到共享集合allOut中
        //        allOut.add(pw);
                synchronized (allOut) {
                    int num = 1;
                    String newNickname = nickname;
                    while (true) {
                        if (allOut.containsKey(newNickname)) {
                            newNickname = nickname + num;
                            num++;
                        } else {
                            allOut.put(newNickname, pw);
                            nickname=newNickname;
                            break;
                        }
                    }
                }

                //广播消息，告知所有客户端该用户上线了
                sendMessage(nickname+"上线了,当前在线人数:"+allOut.size());
                String message;

            /*
            while (true){
                String message= br.readLine();
                if (message.equals("exit")){
                    System.out.println("客户端退出了");
                    break;
                }
                System.out.println("客户都说:"+message);

            }*/
               /*
                readLine用于读取来自客户端发送过来的一行字符串。
                会有几种情况:
                1:当客户端没有发送数据时，此时readLine会阻塞
                2:当客户端发送一行字符串过来时，此时readLine方法会立即返回这行内容
                3:当客户端断开连接
             */
                while ((message = br.readLine()) != null) {
                    if(message.startsWith("@")){
                        sendMessageToSomeOne(message);
                    }else {
                        //输出格式:昵称[IP]说:xxxxxxx
                        sendMessage(nickname + "[" + host + "]说:" + message);
                        //将该消息保存到数据库
                        saveMessage(nickname,null,message);
                    }

                }
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                //处理客户端断开连接后的操作
                //1.将该用户端的输入流从allOut中删除
                synchronized (allOut){
                    //将该输入流存入到共享集合allOut中
         //           allOut.remove(pw);
                    allOut.remove(nickname);
                }

                //通知所有客户端该用户下线了
                sendMessage(nickname+"下线了,当前在线人数:"+allOut.size());
                ///3.关闭socket释放资源
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }

            }

        }
        /*
        将给定信息发送给所有客户端
         */
        public void sendMessage(String message){
            System.out.println(message);
            synchronized (allOut){
//                for(PrintWriter o:allOut){
//                    o.println(message);
//            }
                for (PrintWriter o:allOut.values()){
                    o.println(message);
                }




            }
        }
        private  void sendMessageToSomeOne(String message){
  /*
                私聊格式:
                @昵称:消息
                1:获取对方昵称
                2:根据昵称去allOut中获取对方的输出流
                3:将消息单独发送给对方
             */
            /*
                必要的格式验证
                @.+:.+
             */
            if(message.matches("@.+:.+")){
                //1
                String toNickName= message.substring(1,message.indexOf(":"));
                //2
                if (allOut.containsKey(toNickName)){
                    PrintWriter pw=allOut.get(toNickName);
                    message =message.substring(message.indexOf(":")+1);
                    String toMessage=nickname+"悄悄对你说:"+message.substring(message.indexOf(":")+1);
                    pw.println(toMessage);
                    //将消息保存到数据库中
                    saveMessage(nickname,toNickName,message);
                }else {//对方不存在
                    //用当前用户的昵称获取自己的输出流，通知他没有该用户
                    PrintWriter pw = allOut.get(nickname);
                    pw.println(toNickName + "不在线");

                }
            }else {
                PrintWriter pw=allOut.get(nickname);
                pw.println("私聊格式不正确，正确格式:@对方昵称:消息内容");
            }
        }


   /*
   将聊天室消息保存到数据库中
    */
        private  void saveMessage(String fromUser,String toUser,String message){
            /*
            CREATE TABLE chatinfo(
            id INT PRIMARY KEY AUTO_INCREMENT,  主键
            content VARCHAR(255),               聊天内容
            from_use VARCHAR(30),               谁发的
            to_user VARCHAR(30)                 发给谁
)
             */
            try (Connection conn=DBUtil.getConnection()){
                String sql="INSERT INTO chatinfo(content,from_user,to_user) " +
                        "VALUES(?,?,?)";
                PreparedStatement ps=conn.prepareStatement(sql);
                ps.setString(1,message);
                ps.setString(2,fromUser);
                ps.setString(3,toUser);
                ps.executeUpdate();

            } catch (SQLException e) {

                e.printStackTrace();
            }
        }



    }

}
